Java demo coding - Threads and timing
"Please run this demo on a PIII 600, 128Mb, etc. This is not a minimum specification, but all the timing will be out if a different machine is used"
Things like this in nfo files suck (and you may know the demo I'm referring to.....). Just because you're programming in Java doesn't mean you can neglect basic stuff like this. You'd get laughed out of town if you put a disclaimer like the above in anything else.
Read on to (hopeful) find out how to avoid such blunders.
Threads
A thread of execution is like a separate program that is run in parallel with other programs/threads. Any applet that does a lot of processing (hey, you mean a demo?) needs to run itself in a separate thread so that it doesn't freeze the system.
If you try to put something like
public void start(){
while(true){
// do loads of stuff
}
}
in your Applet class nothing good will happen. Most operating systems run on a messaging system where the OS sends the applications messages and the applications do something in response to these messages. The loop above leaves no time for this processing.
This is where threads come in.
Creating a thread
There are actually two ways of creating a thread.
- Derive a class from the Thread class and put a run() method in to perform your task
- Implement the Runnable interface in an existing class of yours.
We'll use the second method as its simpler and suffices in most cases. To make an Applet that can make itself into a Thread something like the following is used
public class ThreadApplet extends Applet implements Runnable{
Thread runner=null;
public void start(){
// check if the thread running already
if(runner==null){
// create a new thread and grab max priority
runner=new Thread(this);
runner.setPriority(Thread.MAX_PRIORITY);
// set the thread going
runner.start();
}
}
public void stop(){
// check if the thread is running
if(runner!=null){
// stop the thread
runner=null;
}
}
public void run(){
Thread me=Thread.currentThread();
while(me==runner){
// do loads of stuff
}
}
}
The first thing to note is the implements Runnable. This means that the class implements the methods defined in the interface Runnable. (Note: this is how Java handles multiple inheritance). The only method that needs to be implemented for this interface in ThreadApplet is run(). In the function run() is the main loop that is executed by the thread. The loop runs until runner becomes null. Most people just call repaint() in this loop and put all the main code in update(). DO NOT DO THIS. I'll explain in a minute.
The other functions start() and stop() handle the control of our thread execution.
Firstly in start() a Thread must be created from out Runnable class. Because we're coders we want to have priority over other threads so we set MAX_PRIORITY to our Thread (a Thread starts out with NORMAL_PRIORITY).The thread is then started with a call to the Thread.start() function. This is a different function to our start() function. At this point runner is equal to the current thread.
In stop(), runner is just set to null. This causes the while(me==runner) loop in run() to finish and the thread to terminate.
Getting some synchronization
Unfortunately you can never guarantee how much time is spent executing your thread. Different speed processors, background (system) tasks, and other variables can cause wildly different execution times of your run() method. Obviously you need to time your program and dynamically adjust its update speed so that it runs at a constant speed on all machines thereby staying in synch with your music.
We can do this quite easily. When your code first starts execution (say right at the beginning of the run() method before the main loop) we'll initialize a timing variable with
ThenMS=System.currentTimeMillis();
Then in your main demo code (in update(), your main run loop, wherever) put
NowMS=System.currentTimeMillis();
count=NowMS-ThenMS;
if(count>0){
ThenMS=NowMS
// do your frame update
}
This puts how much time (in milliseconds) has elapsed since the last frame update in count. You then use this count value to decide how much to update positions of things, effects, etc. in this frame. The System.currentTimeMillis() does not actually have millisecond accuracy - it depends on what system your Java Virtual Machine is running. This means the value it returns jumps up in steps (perhaps 33ms or so) and it can appear that a frame took no time to render (or a machine may be so fast a frame really does have a sub 1ms update time). Either way, count may equal zero.
What does repaint() do?
I said earlier on not to put all your main code in update(). Now I'll explain why, and where it should be put. Imagine you did put a repaint() in the while loop in your run() method. What happens? repaint() essentially posts a paint message (or something similar) and then returns. Then your program responds to this event by calling the update() method of your applet. This update() is not run in the thread you've carefully created and completely stops your applet responding to any other messages it receives until the update() method has finished executing. If your doing lots of time consuming stuff in update() this really screws stuff up. Try catching a keypress (perhaps to exit the demo) and chances are it'll just get ignored.
The simple solution to call the update() function of your applet explicitly. This means it is executed in the thread and doesn't block up messaging. To do this use
g=this.getGraphics();
before your main loop and then
update(g);
instead of repaint(). update() also needs to be declared as synchronized. This means only one update() process will run at any one time.
It doesn't do anything
Okay, so the example applet (ThreadApplet.java) doesn't draw anything or do fancy stuff but it chugs along nicely minding its own business, ready to have groovy gfx inserted.
Problems, questions? Just email me.